package ru.ifmo.croak.impl.storage.team1;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.ifmo.croak.api.Message;
import ru.ifmo.croak.api.QueryRequest;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * @author Alex Vikharev
 *         22.05.11
 */
public class DatabaseBatchQuery {
    private final static Logger log = LoggerFactory.getLogger(DatabaseBatchQuery.class);
    private final QueryRequest queryRequest;
    private final Connection connection;

    private final String fetchQuery;
    private final String sizeQuery;

    private volatile int size = -1;
    private int maxId = -1;

    public DatabaseBatchQuery(final QueryRequest queryRequest, final Connection connection) throws SQLException {
        this.queryRequest = queryRequest.clone();
        this.connection = connection;
        final int limit = queryRequest.getLimit();
        final String maxIdQuery;
        if (queryRequest.getAuthor() != null && queryRequest.getText() != null) {
            fetchQuery = "select m.* from ft_search_data(?, 0, 0) d left join messages m on d.keys[0]=m.id " +
                    "where m.author = ? and m.id <= ? order by m.time desc limit ? offset ?";
            sizeQuery = "select count(m.*) from ft_search_data(?, 0, 0) d left join messages m on d.keys[0]=m.id " +
                    "where m.author = ? and m.id <= ? limit ? offset ?";
            maxIdQuery = "select max(m.id) from ft_search_data(?, 0, 0) d left join messages m on d.keys[0]=m.id " +
                    "where m.author = ? limit ? offset ?";
        } else if (queryRequest.getAuthor() == null && queryRequest.getText() != null) {
            fetchQuery = "select m.* from ft_search_data(?, 0, 0) d left join messages m on d.keys[0]=m.id " +
                    "where m.id <= ? order by m.time desc  limit ? offset ?";
            sizeQuery = "select count(m.*) from ft_search_data(?, 0, 0) d left join messages m on d.keys[0]=m.id " +
                    "where m.id <= ? limit ? offset ?";
            maxIdQuery = "select max(m.id) from ft_search_data(?, 0, 0) d left join messages m on d.keys[0]=m.id limit ? offset ?";
        } else if (queryRequest.getAuthor() != null && queryRequest.getText() == null) {
            fetchQuery = "select m.* from messages m where m.author = ? and m.id <= ? order by m.time desc limit ? offset ?";
            sizeQuery = "select count(m.*) from messages m where author = ? and m.id <= ? limit ? offset ?";
            maxIdQuery = "select max(m.id) from messages m where author = ? limit ? offset ?";
        } else {
            fetchQuery = "select m.* from messages m where m.id <= ? order by time desc limit ? offset ?";
            sizeQuery = "select count(m.*) from messages m where m.id <= ? limit ? offset ?";
            maxIdQuery = "select max(id) from messages m limit ? offset ?";
        }
        log.debug("Selected query: [{}], query for size: [{}]", fetchQuery, sizeQuery);

        PreparedStatement s = prepareStatement(maxIdQuery, limit, 0);
        log.debug("Executing query for max id {}", s);
        maxId = queryForInt(s);
        log.debug("Max id for this query = {}", maxId);
    }

    private static int queryForInt(PreparedStatement ps) throws SQLException {
        ResultSet rs = ps.executeQuery();
        if (rs.next()) {
            return rs.getInt(1);
        } else {
            return 0;
        }
    }

    private PreparedStatement prepareStatement(String query, int limit, int offset) throws SQLException {
        PreparedStatement statement = connection.prepareStatement(query);
        int paramNum = 0;
        if (queryRequest.getText() != null) {
            statement.setString(++paramNum, queryRequest.getText());
        }
        if (queryRequest.getAuthor() != null) {
            statement.setString(++paramNum, queryRequest.getAuthor());
        }
        if (maxId != -1) {
            statement.setInt(++paramNum, maxId);
        }
        statement.setInt(++paramNum, limit);
        statement.setInt(++paramNum, offset);
        log.trace("Making query: [{}]", statement);
        return statement;
    }

    private ResultSet queryBatch(int limit, int offset) throws SQLException {
        PreparedStatement statement = prepareStatement(fetchQuery, limit, offset);
        return statement.executeQuery();
    }

    private static List<Message> resultSetToArrayList(ResultSet rs, int prefetchSize) throws SQLException {
        List<Message> temp = new ArrayList<Message>(prefetchSize);
        while (rs.next()) {
            Message message = new Message();
            message.setId((UUID) rs.getObject("uuid"));
            message.setTime(rs.getLong("time"));
            message.setAuthor(rs.getString("author"));
            message.setText(rs.getString("text"));
            log.trace("Fetched message: {}", message);
            temp.add(message);
        }
        return temp;
    }

    public List<Message> getBatch(int limit, int offset) throws SQLException {
        return resultSetToArrayList(queryBatch(limit, offset), limit);
    }

    public int getSize() {
        if (size == -1) {
            try {
                PreparedStatement statement = prepareStatement(sizeQuery, queryRequest.getLimit(), 0);
                log.trace("Executing query: [{}]", statement);
                size = Math.min(queryForInt(statement), queryRequest.getLimit());
                log.trace("Query return size: {}", size);
            } catch (SQLException e) {
                log.error("Error fetching data from database", e);
                throw new RuntimeException(e);
            }
        }
        return size;
    }

}
